﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using FastColoredTextBoxNS;

namespace V32VM
{
    public partial class MemoryViewForm : Form, IMemoryUpdatable
    {
        private int NumColumns { get; set; }
        private int NumRows { get; set; }

        private Source _source;
        public Source MemoryStartAddrSource
        {
            get { return _source; }
            private set
            {
                _source = value;
                memoryViewTextBox_TextChanged(null,
                    new TextChangedEventArgs(memoryViewTextBox.Range));
            }
        }
        public uint MemoryStartAddr { get; set; }

        public MemoryViewForm(Source source)
        {
            InitializeComponent();

            previousButton.Click += ((sender, e) => previousMemory());
            nextButton.Click += ((sender, e) => nextMemory());

            NumColumns = 20;
            NumRows = 20;

            _source = source;
            UpdateMemory();
        }

        private void updateText()
        {
            this.Text = "Memory View: " + MemoryStartAddrSource.ToString() + (MemoryStartAddrSource is ConstantSource ? "" : " = " + MemoryStartAddrSource.GetValue());
        }

        public void UpdateMemory()
        {
            MemoryStartAddr = MemoryStartAddrSource.GetValue();
            updateText();
            LoadMemory();
        }

        public void LoadMemory()
        {
            MemoryStartAddr = (uint)((MemoryStartAddr / NumColumns) * NumColumns);

            byte[,] memory = new byte[NumRows, NumColumns];
            for (uint offset = 0; offset < NumRows * NumColumns; offset++)
                memory[offset % NumColumns, offset / NumColumns] = Program.VM.memory[Program.VM.regs.ip, MemoryStartAddr + offset];

            string text = "          |";
            for (int col = 0; col < NumColumns; col++)
                text += String.Format("{0,-3} ", col);
            text = text.TrimEnd();
            text += Environment.NewLine + "----------+" + new string('-', 4 * NumColumns).Remove(4 * NumColumns - 1) + Environment.NewLine;
            for (int row = 0; row < NumRows; row++)
            {
                uint rowAddress = MemoryStartAddr + (uint)(row * NumColumns);
                text += String.Format("{0:0000000000}|", rowAddress);
                for (int col = 0; col < NumColumns; col++)
                    text += String.Format("{0:000} ", memory[col, row]);
                text = text.TrimEnd();
                if (row != NumRows - 1)
                    text += Environment.NewLine;
            }
            memoryViewTextBox.Text = text;

            memoryViewTextBox_MouseMove(null, null);
        }

        private void previousMemory()
        {
            MemoryStartAddr -= (uint)NumColumns;
            LoadMemory();
        }
        private void nextMemory()
        {
            MemoryStartAddr += (uint)NumColumns;
            LoadMemory();
        }

        private void previousPage()
        {
            MemoryStartAddr -= (uint)(NumRows * NumColumns);
            LoadMemory();
        }
        private void nextPage()
        {
            MemoryStartAddr += (uint)(NumRows * NumColumns);
            LoadMemory();
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (keyData == Keys.P)
            {
                previousMemory();
                return true;
            }
            else if (keyData == Keys.N)
            {
                nextMemory();
                return true;
            }
            else if (keyData == (Keys.Control | Keys.P))
            {
                previousPage();
                return true;
            }
            else if (keyData == (Keys.Control | Keys.N))
            {
                nextPage();
                return true;
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }

        private void memoryViewTextBox_MouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                uint memAddress = getAddressUnderCursor();
                uint wordVal = getWordValUnderCursor();
                wordValueLabel.Text = String.Format("Word under Cursor ({0:0000000000}): {1}",
                        memAddress, wordVal);
            }
            catch (Exception)
            {
                wordValueLabel.Text = "Word under Cursor: N/A";
            }
        }

        private void memoryViewTextBox_MouseLeave(object sender, EventArgs e)
        {
            wordValueLabel.Text = "Word under Cursor: N/A";
        }

        private uint getAddressUnderCursor()
        {
            Point mouseLoc = memoryViewTextBox.PointToClient(Cursor.Position);
            Place place = memoryViewTextBox.PointToPlace(mouseLoc);
            int lineNum = place.iLine;
            int lineCol = place.iChar;

            //int charIndex = memoryViewTextBox.GetCharIndexFromPosition(mouseLoc);
            //int lineNum = memoryViewTextBox.GetLineFromCharIndex(charIndex);
            //int startOfLineIndex = memoryViewTextBox.GetFirstCharIndexFromLine(lineNum);
            //int lineCol = charIndex - startOfLineIndex;

            if (lineCol > 10 && lineNum > 1) // 10 is length of row header (address) + '|'.
            {
                lineCol -= 11;
                if (lineCol % 4 != 3)
                {
                    int memX = lineCol / 4; // 4 is length of memory cell + ' '.
                    int memY = lineNum - 2; // remove column headers/underline

                    uint memOffset = (uint)(memX + memY * NumColumns);
                    uint memAddress = MemoryStartAddr + memOffset;
                    return memAddress;
                }
            }
            throw new Exception("No address under cursor!");
        }
        private uint getWordValUnderCursor()
        {
            try
            {
                uint memAddress = getAddressUnderCursor();
                return Program.VM.memory.words[Program.VM.regs.ip, memAddress];
            }
            catch (Exception)
            {
                throw new Exception("No word value under cursor!");
            }
        }

        private void goToAddressButton_Click(object sender, EventArgs e)
        {
            MemoryStartAddr = addressUnderCursor;
            LoadMemory();
        }
        private void goToAddressUnderCursorToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MemoryStartAddr = Program.VM.memory.words[Program.VM.regs.ip, addressUnderCursor];
            LoadMemory();
        }
        private void openViewStartingHereButton_Click(object sender, EventArgs e)
        {
            MemoryViewForm memView = new MemoryViewForm(new ConstantSource(addressUnderCursor));
            memView.UpdateMemory();
            memView.Show();
        }
        private void openViewAtAddressButton_Click(object sender, EventArgs e)
        {
            MemoryViewForm memView = new MemoryViewForm(new IndirectSource(new ConstantSource(addressUnderCursor)));
            memView.UpdateMemory();
            memView.Show();
        }
        private void setSourceToAddressButton_Click(object sender, EventArgs e)
        {
            this.MemoryStartAddrSource = new ConstantSource(addressUnderCursor);
            updateText();
        }
        private void setSourceToAtAddressButton_Click(object sender, EventArgs e)
        {
            this.MemoryStartAddrSource = new IndirectSource(new ConstantSource(addressUnderCursor));
            updateText();
        }

        private uint addressUnderCursor;
        private void memoryViewTextBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                try
                {
                    addressUnderCursor = getAddressUnderCursor();
                    uint word = Program.VM.memory.words[Program.VM.regs.ip, addressUnderCursor];
                    goToAddressButton.Text = "Go to " + addressUnderCursor;
                    goToAddressUnderCursorButton.Text = "Go to " + word;
                    openViewStartingHereButton.Text = "Open View Starting at " + addressUnderCursor;
                    openViewAtAddressButton.Text = "Open View Starting at " + word;
                    setSourceToAddressButton.Text = "Set Source to " + addressUnderCursor;
                    setSourceToAtAddressButton.Text = "Set Source to " + word;
                    memoryContextMenu.Show(memoryViewTextBox, e.Location);
                }
                catch (Exception) { }
            }
        }

        private void flattenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _source = this.MemoryStartAddrSource.Flatten();
            updateText();
        }
        private void editToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SourceEditDialog sourceEdit = new SourceEditDialog(MemoryStartAddrSource);
            if (sourceEdit.ShowDialog(this) == DialogResult.OK)
            {
                this.MemoryStartAddrSource = sourceEdit.Source;
                updateText();
            }
        }

        private void goToSourceToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.UpdateMemory();
        }

        private void MemoryViewForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            MainForm.ViewForms.Remove(this);
        }

        #region FastColoredTextBox Stuff
        TextStyle headerStyle = new TextStyle(Brushes.Teal, null, FontStyle.Regular);
        TextStyle sourceStyle = new TextStyle(Brushes.Red, null, FontStyle.Underline);
        TextStyle watchStyle = new TextStyle(Brushes.Blue, null, FontStyle.Underline);

        private void memoryViewTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            e.ChangedRange.ClearStyle(headerStyle, sourceStyle);
            e.ChangedRange.SetStyle(headerStyle, @"[0-9]{10}");
            e.ChangedRange.SetStyle(headerStyle, @"\b([0-9]{1}|[0-9]{2})\b");

            foreach (Watch watch in Watch.Watches)
            {
                if (watch.IsValueWatch) continue;

                Tuple<uint, bool> highlightAddress = watch.GetHighlightAddress();
                if (highlightAddress == null) continue;
                foreach (Range range in (highlightAddress.Item2 ?
                    getRangesFromWordAddress(highlightAddress.Item1) :
                    new[] { getRangeFromAddress(highlightAddress.Item1) }))
                    if (range != null)
                        range.SetStyle(watchStyle);
            }

            foreach (Range range in getRangesFromWordAddress(MemoryStartAddrSource.GetValue()))
                if (range != null)
                    range.SetStyle(sourceStyle);
        }

        private Range getRangeFromAddress(uint address)
        {
            if (!isInText(address)) return null;
            address -= MemoryStartAddr;
            uint memX = address % (uint)NumColumns,
                 memY = address / (uint)NumColumns;

            int lineNum = (int)memY + 2;
            int lineCol = (int)memX * 4 + 11;
            return new Range(memoryViewTextBox, new Place(lineCol, lineNum), new Place(lineCol + 3, lineNum));
        }
        private IEnumerable<Range> getRangesFromWordAddress(uint address)
        {
            for (uint offset = 0; offset < 4; offset++)
                yield return getRangeFromAddress(address + offset);
        }
        private bool isInText(uint address)
        {
            address -= MemoryStartAddr;
            uint memX = address % (uint)NumColumns,
                 memY = address / (uint)NumColumns;

            return memX < NumColumns && memY < NumRows;
        }
        #endregion

        private void vScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            MemoryStartAddr += (uint)(NumColumns * e.NewValue);
            e.NewValue = 0;
            LoadMemory();
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            MemoryStartAddr -= (uint)(NumColumns * e.Delta / 120 * 4); // 120 == WHEEL_DELTA?
            LoadMemory();
            base.OnMouseWheel(e);
        }
    }
}
